/******************************************************************************
*
* This file is part of FreeTrack (http://code.google.com/p/qtfreetrack).
* BY Willy Gardiol (willy@gardiol.org)
*
* FreeTrack is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FreeTrack is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar.  If not, see <http://www.gnu.org/licenses/>
*
******************************************************************************/
#include "oauthmanager.h"
#include "configuration.h"

#include "controls/ftlabel.h"
#include "controls/ftpushbutton.h"
#include "controls/ftmessagebox.h"
#include "controls/ftvboxlayout.h"

extern Configuration* configuration;

/*
 when updating to support more OAuth sites, create a "settings" class and keep this
 class as general as possible
*/

#define BREADCRUMBS_KEY "mv1XwR9l7eFKTQR8nHlT"
#define BREADCRUMBS_SECRET "isuvyorxNqiKBizIpSvPgVyG7zlnPJVnb4yLlVr3"
#define BREADCRUMBS_URL "http://api.gobreadcrumbs.com"


OauthManager::OauthManager(QObject *parent, QByteArray t, QByteArray ts) :
    QObject(parent),
    status(OauthManager::NOAUTH),
    baseUrl(BREADCRUMBS_URL),
    consumerSecret(BREADCRUMBS_SECRET),
    consumerKey(BREADCRUMBS_KEY),
    error(false)
{
    token = t;
    tokenSecret = ts;
    if ( (t != "") && ( ts != "" ) )
        status = OauthManager::AUTH;
}

OauthManager::~OauthManager()
{
}

void OauthManager::openWait(QString title, QNetworkAccessManager* network_manager, QNetworkReply* reply, qint64 size )
{
    FTMessageBox* dialog = FTMessageBox::Progress( title, title, 0, size );

    connect( network_manager, SIGNAL(finished(QNetworkReply*)),
                dialog, SLOT(accept()) );
    connect( reply, SIGNAL(downloadProgress(qint64,qint64)),
            dialog, SLOT(updateProgress(qint64,qint64)) );
    connect( reply, SIGNAL(uploadProgress(qint64,qint64)),
            dialog, SLOT(updateProgress(qint64,qint64)) );
    dialog->exec();
    delete dialog;
}

bool OauthManager::getRequestToken()
{
    error = false;
    // For the request token we need consumerSecret and consumerKey...
    if ( consumerSecret.isEmpty() || consumerKey.isEmpty() )
    {
        error = true;
        return false;
    }
    status = OauthManager::NOAUTH;

    QString url = baseUrl + "/oauth/request_token";
    token.clear();
    tokenSecret.clear();
    OauthParams ret_params;
    performRequest(url,
                   token,
                   tokenSecret,
                   ret_params,
                   QString("POST"),
                   tr("Requesting authorization..."));
    if ( !error )
    {
        ret_params = processRawReply();
        token = ret_params.value( "oauth_token" );
        tokenSecret = ret_params.value( "oauth_token_secret" );
        status = OauthManager::WAIT_PIN;
    }
    return !error;
}

bool OauthManager::getAccessToken(const QByteArray& pin)
{
    error = false;
    if ( token.isEmpty() ||
         tokenSecret.isEmpty() ||
         status != OauthManager::WAIT_PIN )
    {
        error = true;
        return false;
    }

    QString url = baseUrl + "/oauth/access_token";
    OauthParams params;
    params.insert( "oauth_verifier", pin );

    performRequest(url,
                   token,
                   tokenSecret,
                   params,
                   QString("POST"),
                   tr("Authorizing PIN..."));
    if ( !error )
    {
        OauthParams ret_params = processRawReply();
        status = OauthManager::AUTH;
        token = ret_params.value( "oauth_token" );
        tokenSecret = ret_params.value( "oauth_token_secret" );
    }
    return !error;
}

QByteArray OauthManager::getData( QString url,
                                   const OauthParams& params,
                                   QString title)
{
    error = false;
    if ( status != OauthManager::AUTH )
    {
        error = true;
        return QByteArray();
    }
    return performRequest(baseUrl + "/" + url,
                    token,
                    tokenSecret,
                    params,
                    QString("GET"),
                    title);
}

QByteArray OauthManager::postData( QString url,
                                   const OauthParams& params,
                                   QString title)
{
    error = false;
    if ( status != OauthManager::AUTH )
    {
        error = true;
        return QByteArray();
    }
    return performRequest(baseUrl + "/" + url,
                                            token,
                                            tokenSecret,
                                            params,
                                            QString("POST"),
                                            title);
}

QByteArray OauthManager::performRequest( const QString &const_url,
                                          const QByteArray &token,
                                          const QByteArray &tokenSecret,
                                          const OauthParams &params,
                                          QString mode,
                                          QString title)
{
    QString url = const_url;
    OauthParams localParams = params;

    // timestamp
    uint time = QDateTime::currentDateTime().toTime_t();
    QByteArray timestamp = QByteArray::number( time );
    // nonce
    QByteArray nonce;
    qsrand( time );
    for (int p = 0; p < 16; p++ )
        nonce += QString("%1").arg( qrand()%10 );

    // Add required parameters...
    localParams.insert("oauth_nonce", nonce);
    localParams.insert("oauth_timestamp", timestamp);
    localParams.insert( "oauth_consumer_key", consumerKey );
    localParams.insert( "oauth_signature_method", "HMAC-SHA1" );
    localParams.insert( "oauth_version", "1.0" );
    localParams.insert( "oauth_token", token );

    // create signature
    error = false;

    QByteArray signature = sign( url,
                                 token,
                                 tokenSecret,
                                 localParams,
                                 mode);

    if ( error )
        return QByteArray();

    // add signature to parameters
    localParams.insert( "oauth_signature", signature );

    if ( configuration->internetAuthorized() )
    {
        QNetworkAccessManager* network_manager;
        network_manager = new QNetworkAccessManager;
        connect( network_manager, SIGNAL(finished(QNetworkReply*)),
                 this, SLOT(parseReply(QNetworkReply*)) );

        QNetworkRequest request;
        QByteArray header;

        if ( mode == "POST" )
        {
            request.setHeader(QNetworkRequest::ContentTypeHeader,
                          "application/x-www-form-urlencoded" );
            header = convertParams( localParams, "=", "&" );
        }
        else if ( mode == "GET" )
        {
            if ( params.count() > 0 )
                url += "?"+convertParams( params, "=", "&" );
            header = "OAuth "+convertParams( localParams, "=\"", "\"," );
            request.setRawHeader( "Authorization", header );
        }

        request.setUrl( QUrl( url ) );

        error = true;
        rawReply.clear();

        // Setup the request...
        QNetworkReply *reply = NULL;

        if ( mode == "POST" )
            reply = network_manager->post( request, header );
        else if ( mode == "GET" )
            reply = network_manager->get( request );

        // start the event loop and wait for the response
        openWait( title, network_manager, reply, header.size() );
        if ( reply != NULL )
            delete reply;
        delete network_manager;
    }
    return rawReply;
}

QByteArray OauthManager::sign( const QString &url,
                               const QByteArray &, //token,
                               const QByteArray &tokenSecret,
                               OauthParams& params,
                               QString mode)
{
    // Prepare string to be signed. Start with HTTP method
    QByteArray baseString = mode.toAscii();
    // add the URL (%encoded)
    baseString.append("&");
    baseString.append(url.toAscii().toPercentEncoding());
    // add all the parameters
    baseString.append("&");
    baseString.append( convertParams( params, "=", "&").toPercentEncoding() );

    QByteArray key = consumerSecret.toPercentEncoding() + "&" + tokenSecret.toPercentEncoding();

    int blockSize = 64; // HMAC-SHA-1 block size, defined in SHA-1 standard
    if (key.length() > blockSize)
    { // if key is longer than block size (64), reduce key length with SHA-1 compression
        key = QCryptographicHash::hash(key, QCryptographicHash::Sha1);
    }

    QByteArray innerPadding(blockSize, char(0x36)); // initialize inner padding with char "6"
    QByteArray outerPadding(blockSize, char(0x5c)); // initialize outer padding with char "\"
    // ascii characters 0x36 ("6") and 0x5c ("\") are selected because they have large
    // Hamming distance (http://en.wikipedia.org/wiki/Hamming_distance)

    for (int i = 0; i < key.length(); i++) {
        innerPadding[i] = innerPadding[i] ^ key.at(i); // XOR operation between every byte in key and innerpadding, of key length
        outerPadding[i] = outerPadding[i] ^ key.at(i); // XOR operation between every byte in key and outerpadding, of key length
    }

    // result = hash ( outerPadding CONCAT hash ( innerPadding CONCAT baseString ) ).toBase64
    QByteArray total = outerPadding;
    QByteArray part = innerPadding;
    part.append(baseString);
    total.append(QCryptographicHash::hash(part, QCryptographicHash::Sha1));
    QByteArray hashed = QCryptographicHash::hash(total, QCryptographicHash::Sha1);
    return hashed.toBase64().toPercentEncoding();
}

QByteArray OauthManager::convertParams(const OauthParams& params,
                                       QString middle,
                                       QString end)
{
    QByteArray param;
    QByteArray return_string;
    Q_FOREACH( param, params.uniqueKeys() )
    {
        QList<QByteArray> values = params.values( param );
        if ( values.size() > 1 ) {
            qSort( values.begin(), values.end() );
        }
        QByteArray value;
        Q_FOREACH ( value, values ) {
            return_string.append( param );
            return_string.append( middle );
            return_string.append( value );
            return_string.append( end );
        }
    }
    // remove the trailing end character (comma or ampersand)
    return_string.chop(1);
    return return_string;
}

bool OauthManager::getToken(QByteArray& retToken, QByteArray& retTokenSecret)
{
    if ( status == OauthManager::AUTH )
    {
        retToken = token;
        retTokenSecret = tokenSecret;
        return true;
    }
    return false;
}

OauthManager::OauthStatus OauthManager::getStatus()
{
    return status;
}

OauthParams OauthManager::processRawReply()
{
    OauthParams requestReply;
    QList<QByteArray> params = rawReply.split( '&' );
    QByteArray param;
    QByteArray key;
    int index;
    // iterate through name=value pairs
    Q_FOREACH ( param, params )
    {
        index = param.indexOf( '=' );
        // key is on the left
        key = param.left( index );
        // value is on the right
        requestReply.insert( key , param.right( param.length() - index - 1 ) );
    }
    return requestReply;
}

void OauthManager::parseReply(QNetworkReply *reply)
{
    int code =  reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt();
    rawReply.clear();
    error =  !( (code >= 200)&&(code < 300) );
    rawReply = reply->readAll();
    reply->close();
}

bool OauthManager::getError()
{
    return error;
}

QString OauthManager::getAuthUrl()
{
    if ( status == OauthManager::WAIT_PIN )
    {
        QString url = baseUrl+"/oauth/authorize?oauth_token="+token;
       return url;
   }
    return "";
}
